<?php
/**
*
*
*/
abstract class Yz_Application_Abstract
    extends Yz_Class
    implements Yz_Application_Interface
{

    /**
    *   @var array
    */
    protected static $modules = array();

    /**
    *   @var array
    */
    protected static $namespaces;

    /**
     *  @var string
     */
    protected static $name;

    /**
    *   @var Yz_Config
    */
    protected static $config;


    /******************************
    ***      Public methods     ***
    *******************************/


    /** get the application-wide configuration object
     *  @return Yz_Config
     */
    public static function getConfig()
    {
        return Yz::$config;
    }


    /** fetch array of InstalledNamespaces from config
     *  @return array
     */
    public static function getInstalledNamespaces()
    {
        Log::log(null, Log::DEBUG4);
        $n =& Yz::$namespaces;
        return $n;
    }


    /** get the application's name
     *  @return string
     */
    public static function getName()
    {
        Log::log(null, Log::DEBUG4);
        return Yz::$name;
    }

    /** get the application's main namespace
     *  @return string
     */
    public static function getAppNamespace()
    {
        Log::log(null, Log::DEBUG4);
        return Yz::$name;
    }

    /** load module by its short name
    *   @param string selector
    *   @return module object
    *   @throw Yz_Exception_ModuleNotFound
    *   @throw Yz_Exception_InvalidArgument
    *   @throw Yz_Exception_InvalidValueType
    */
    public static function selectModule( $moduleName )
    {
        Log::log(null, Log::DEBUG);
        if (! Yz_String::isValidIdentifier($moduleName))
            throw TypeError::_new_();

        $modules =& Yz::$modules;

        if (isset($modules[$moduleName]))
            return $modules[$moduleName];

        foreach (Yz::$namespaces as $ns) {
            $moduleClass = sprintf("%s\\modules\\%s\\Module",
                $ns,
                $moduleName
            );
            $class = Yz::loadClass($moduleClass);
            if (false !== $class) {
                $module = $class::getInstance();
                $modules[$moduleName] = $module;
                return $module;
            }
        }
        throw Yz_Exception_ModuleNotFound::_new( array("moduleName" => $moduleName) );
    }

    /**
     *  User-API method to load a class or verify that it's loaded
     *   @param string className
     *   @return className|false
     */
    public static function loadClass( $className )
    {
        return Autoloader::loadClass($className);
    }

    /** Load a class (in the following order):
     *      - as the classname in the application's name as top-level namespace
     *      - as the classname prefixed with 'Yz_' (in root namespace)
     *  @param string className
     *  @return className|false
     */
    public static function loadClassNS( $className )
    {
        assert('is_string($className)');
        $class = Autoloader::loadClass(Yz::$name . '\\' . $className)
            or $class = Autoloader::loadClass('Yz_' . $className);
        return $class;
    }

    /**
     *
     */
    public static function _destruct_()
    {
        Log::log( sprintf("Included files: %d, classes: %d, interfaces: %d",
                count(get_included_files()),
                count(get_declared_classes()),
                count(get_declared_interfaces())
            ),
            Log::DEBUG
        );
        Log::log("Used memory: ".memory_get_usage(), Log::DEBUG);
        Log::log("END_LOG", Log::INFO);
    }


    /******************************
    ***     Protected methods   ***
    *******************************/

    /**
     *  final protected constructor: prevent any code execution within it
     */
    final protected function __construct() { }


    /**
    *   @return (ignored)
    */
    protected static function displayErrorMessage( $message )
    {
        file_put_contents( 'php://stderr', "[Yz]: ERROR: $message.\n" );
    }


    /**
     *
     */
    protected static function initConfig()
    {
        $myconfig = new Yz_Config( null, 'App.Config' );
        $files = array('config.php');
        $names = explode('\\', get_called_class()); // e.g. Test\Application => (Test, Application)
        $c = "";
        foreach ($names as $n) {
            $c .= '.' . $n;
            $files[] = "config{$c}.php";    // config.Test.php, config.Test.Application.php
        }
        foreach ($files as $cf) {
            $file = YZ_LIB . $cf;
            Log::log("Include config file '{$file}'", Log::DEBUG);

            if (is_file($file) and is_readable($file)) {

                $configString = include( $file );
                assert('is_string($configString)');
                if ( "" !== "$configString" )
                    $myconfig = Yz_Config_Loader::load( $configString, 'App.Config', $myconfig );
            }

        }
        Yz::$config = $myconfig;
    }

    /** setup the php `error' and exception handlers
     *  @return void
     */
    protected static function initErrorHandlers()
    {
        // force all php 'errors' to be handled
        error_reporting( -1 );

        // set the error handler
        set_error_handler( 'Yz::errorHandler' );

        // set the exception handler
        set_exception_handler( 'Yz::exceptionHandler' );

        // Activate assert w/o warnings and make it quiet
        assert_options(ASSERT_ACTIVE, 1);
        assert_options(ASSERT_WARNING, 0);
        assert_options(ASSERT_QUIET_EVAL, 0);
        // set the exception handler
        assert_options(ASSERT_CALLBACK, 'Yz::assertHandler' );

        // shutdown
        register_shutdown_function('Yz::_destruct_');
    }

    /** init log and add a writer for ERROR level into STDERR
     *  @return this
     */
    protected static function initLog()
    {
        $logClass = Yz::loadClassNS('Log_Log');
        class_alias( $logClass, 'Log' );
        Log::init();
    }


    /** load the i18n default handler class and create alias `I18N` for the class
     *  @return this
     */
    protected static function initI18N()
    {
        Log::log(null, Log::DEBUG);
        $class = Yz::loadClassNS('I18N_Default');
        class_alias($class, 'I18N');
    }

    /*************************
     *      Error handlers   *
     *************************/

    /** handler for php dumb warnings: ALWAYS log ALL warnings!
     *  @return void
     *  @throw Yz_Exception
     */
    public static function errorHandler( $errno, $errstr, $errfile, $errline )
    {
        switch ( $errno ) {
            case E_WARNING:
            case E_USER_WARNING:
                $error = 'WARNING';
                break;
            case E_NOTICE:
            case E_USER_NOTICE:
                $error = 'NOTICE';
                break;
            case E_STRICT:
                $error = 'STRICT';
                break;
            case E_DEPRECATED: // since 5.3.0
            case E_USER_DEPRECATED:
                $error = 'DEPRECATED';
                break;
            case E_RECOVERABLE_ERROR: // since 5.2.0
                throw new Yz_Exception("PHP Recoverable error '{$errstr}' at {$errfile}@{$errline}");
            default:
                throw new Yz_Exception("Unrecognized PHP warning code: $errno");
        } // switch
        if (0 === error_reporting())
            $error = '@' . $error;

        Log::log( "{$errfile}@{$errline}: PHP {$error}: {$errstr}", Log::ERROR );
        return;
    }



    /**
     *  exceptionHandler() : Define default exception handler
     *  @return exit
     */
    public static function exceptionHandler( Exception $ex )
    {
        if ( $ex instanceof Yz_Exception ) {
            $msg = $ex->toString();
        } else {
            $msg = sprintf( "Unhandled Exception '%s' in file '%s' at line '%d':\n%s",
                get_class( $ex ),
                $ex->getFile(),
                $ex->getLine(),
                $ex->getMessage()
            );
        }
        $exText = "$msg\nStack trace: " . $ex->getTraceAsString();

        Log::log( date('r'), Log::FATAL );
        Log::log( $exText, Log::FATAL );

        Yz::displayErrorMessage( $exText );
        exit( 1 );
    }

    /**
     *  assertHandler() : Define default assert handler
     *  @return exit
     */
    public static function assertHandler( $file, $line, $code )
    {
        $msg = "Assertion failed in {$file} at line {$line}: {$code}";
        Log::log( $msg, Log::FATAL );

        throw AssertError::_new_(array(
            'srcfile' => $file,
            'srcline' => $line,
            'srccode' => $code,
        ));
    }

    protected static function init()
    {
        defined( 'YZ_TIME' ) or define( 'YZ_TIME', microtime(true) );
        defined( 'YZ_ROOT' ) or define( 'YZ_ROOT', realpath('.') . '/' );
        $self = get_called_class();
        class_alias( $self, 'Yz' );
        Yz::$name = $ns = $self::_ns_();
        $n =& Yz::$namespaces;
        $n = array($self);
        if ('Yz' !== $self)
            $n[] = 'Yz';
        $self::initLog();
        $self::initErrorHandlers();
        $self::initConfig();
        $self::initI18N();
    }

} // class

